classdef PacketsManager < Singleton & ClockHandler
   properties % Public Access
       current_packet_id = 1;
       current_multi_packet_id = 1;
       delivered_multi_packets = [];
       
       template_packets_for_this_tick = [];
       number_of_packets_per_template_for_this_tick = [];
   end
   
   methods(Access = private)
       function obj = PacketsManager()
           obj.current_packet_id = 1;
           obj.current_multi_packet_id = 1;
           obj.delivered_multi_packets = [];
           %register on clock tick
            ck = ClockSingleton.instance();
            ck.register_for_clock_ticks(obj);
       end
   end
   
   methods(Static)
       function obj = instance(should_reinit)
         persistent unique_instance;
         should_reinit = (nargin>0);
         if isempty(unique_instance) || (should_reinit==true)
            unique_instance = PacketsManager();
         end
         obj = unique_instance;
      end
   end
   
   methods % Public Access
       function add_packet_to_delivered_packets(obj, multi_packet)
            utils = Utils.instance();
            utils.trace(sprintf('PacketsManager: packet %d delivered  to destination node %d \n', multi_packet.id, multi_packet.packet.dest_node.id));
            
            packet = multi_packet.packet;
            ck = ClockSingleton.instance();
            packet.delivery_time_in_msec = ck.current_time_in_msec;
            obj.delivered_multi_packets = [obj.delivered_multi_packets multi_packet];
            
            %update the nodes received data (raw, not effective)
            multi_packet.packet.dest_node.update_received_data_size(multi_packet.packet.size_in_bytes*multi_packet.num_of_packets);
       end
       
       function total_num_of_packets_delivered = get_num_of_delivered_packets(obj)
           total_num_of_packets_delivered = 0;
           if isempty(obj.delivered_multi_packets)
               return;
           end
           
           total_number_of_duplicate_packets = 0;
           for i=1:length(obj.delivered_multi_packets)
               total_number_of_duplicate_packets = total_number_of_duplicate_packets+obj.delivered_multi_packets(i).num_of_packets;
           end
           
           ids_vec(total_number_of_duplicate_packets) = 0;
           ids_index =1;
           %build a packet ids vector
           for i=1:length(obj.delivered_multi_packets)
               curr_multi_packet = obj.delivered_multi_packets(i);
               for j=curr_multi_packet.packet_id_range_min:curr_multi_packet.packet_id_range_max
                   ids_vec(ids_index) = j;
                   ids_index=ids_index+1;
               end
           end
           ids_vec = ids_vec(1:(ids_index-1));
           ids_vec = unique(ids_vec);
           total_num_of_packets_delivered = length(ids_vec); 
       end
       
       function ret = get_next_multi_packet_id(obj)
           ret = obj.current_multi_packet_id;
           obj.current_multi_packet_id = obj.current_multi_packet_id+1;
       end
       
       function ratio = calc_delivery_rate(obj)
           num_of_delivered_packets = obj.get_num_of_delivered_packets();
           
           utils = Utils.instance();
            utils.trace(sprintf('PacketsManager: delivered %d out of %d packets \n', num_of_delivered_packets,obj.current_packet_id -1));
            
           ratio = num_of_delivered_packets/(obj.current_packet_id -1);
       end
       
       function calc_effective_throughput(obj)
       end
       
       function template_packet = create_template_packet(obj, source_node, dest_node, packet_size_in_bytes)
            id = 0;
            romer_alpha = ParamsSingleton.instance().romer_alpha;
            romer_c_source = source_node.distance_vector(dest_node.id);
            remaining_credit = (1+romer_alpha) * romer_c_source;
            packet_route = [];
            template_packet = RomerPacket(id, packet_size_in_bytes, source_node, dest_node, packet_route, romer_alpha, remaining_credit, romer_c_source);
       end
       
       function create_new_packet(obj, packet_template)
           obj.inc_number_of_packets_generated_in_this_tick_by_template(packet_template);
       end
       
       function clock_tick_handler(obj)
           if ~isempty(obj.template_packets_for_this_tick)
               obj.deposit_all_packets_generated_in_this_tick();
           end
           %CR - should we delete the template array?? probably not
           obj.template_packets_for_this_tick = [];
           obj.number_of_packets_per_template_for_this_tick = [];
       end
       
       function deposit_all_packets_generated_in_this_tick(obj)
           global_depositor = GlobalPacketDepositor.instance();
           ck = ClockSingleton.instance();
           %generate packets from all templates and deposit them
           for i=1:length(obj.template_packets_for_this_tick)
               %create a single multi packet
               curr_template = obj.template_packets_for_this_tick(i);
               num_of_packets_to_create = obj.number_of_packets_per_template_for_this_tick(i);
               new_multi_packet_id = obj.get_next_multi_packet_id();
               new_multi_packet = MultiPacket(new_multi_packet_id, curr_template.duplicate_packet(),obj.current_packet_id,obj.current_packet_id+(num_of_packets_to_create-1));
               obj.current_packet_id =obj.current_packet_id+num_of_packets_to_create;
               new_multi_packet.packet.creation_time_in_msec = ck.current_time_in_msec;
               global_depositor.deposit_packet_to_node(curr_template.src_node.id, new_multi_packet);
           end
       end
       
       function inc_number_of_packets_generated_in_this_tick_by_template(obj, packet_template)
           for i=1:length(obj.template_packets_for_this_tick)
               curr_template = obj.template_packets_for_this_tick(i);
               if curr_template==packet_template
                   obj.number_of_packets_per_template_for_this_tick(i) = obj.number_of_packets_per_template_for_this_tick(i)+1;
                   return;
               end
           end
           %insert the new template
           obj.number_of_packets_per_template_for_this_tick(length(obj.template_packets_for_this_tick)+1) = 1;
           obj.template_packets_for_this_tick = [obj.template_packets_for_this_tick packet_template];
       end
       
   end
   
end